Skip to content

wacli sign-in: a paired wacli reads as signed in, "already signed in" instead of waiting, and a sign-in that shows nothing ends with a reason (#98) - #100

Merged
immakermatty merged 3 commits into
mainfrom
claude/DEV-6626-wacli-sign-in
Sep 29, 2026
Merged

immakermatty merged 3 commits into
mainfrom
claude/DEV-6626-wacli-sign-in

Conversation

@immakermatty

@immakermatty immakermatty commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What the operator saw

On a hosted personal Machine (Launchpad 0.1.8-rc.7 as a systemd user unit), Settings → Tools listed wacli 0.18.2 as "Nepřihlášeno" (not signed in) with a "Přihlásit" button. The operator clicked it. The dialog showed the step "Waiting for you: in progress" and the sentence "Starting the sign-in…" twice, as the status line and as the detail, and no QR code ever appeared. Afterwards the Launchpad's journal had no line about the sign-in. The same dialog signed in to composio without a problem.

Proven cause on the Machine: wacli was already signed in, and the Platform could not see it

Observed on the Machine with the operator's account, with the unit's PATH and HOME, no TTY and stdin /dev/null (only event kinds and shapes were recorded, never values):

  • wacli auth status --json answers {"success":true,"data":{"authenticated":true,"linked_jid":…,"phone":…},"error":null}.
  • wacli auth --events --idle-exit 30s emits auth_starting, warning, connected (0.8 s), offline_sync_completed, warning ×2 and app_state_recovery_requested, then idle_exit (33 s), one plain text line, and exit status 0. There is no qr_code event, because the store was paired before.

What the code did with that:

  1. The sign-in state probe read the wrong fields. The catalog probe for wacli is wacli auth status --json --read-only. It is run from the Launchpad on page load and on refresh (signIn: true), for 10 s, with only PATH/HOME/XDG. It read authenticated, phone and linked_jid at the top level of the answer (src/tools/catalog.ts wacli signInProbe, flag: ["authenticated"]). wacli 0.18.2 wraps every --json answer in {"success","data","error"} (internal/out/out.go, WriteJSON). So at(document, ["authenticated"]) was undefined, and readSignIn returned signed-out (src/tools/status.ts:351). Every paired wacli therefore showed as "Nepřihlášeno"; the state was "not signed in", not "unknown", which shows the probe did run. The test stand-ins had used the same top-level shape, so the tests never caught it.
  2. The sign-in flow waited for a challenge that never comes. connected triggered the probe, and the probe said "not yet" because of point 1. The session then waited for a QR code that an already-paired wacli never prints.
    • On main, a stand-in that replays the observed event stream ends as failed with not-confirmed once the process exits. On the Machine that is after about 33 s.
    • Until then, the dialog showed "Starting the sign-in…": twice while the start request was out (up to 20 s), and in the detail afterwards.

Further hang paths, fixed as additional hardening

  • The same sentence twice: before this PR, startLogin (src/launchpad/tools-panel.ts:346-348) put toolsLoginStarting into both the status line and the body.
  • No deadline in the page: the start and poll requests had none (src/launchpad/ui.ts:231-243), and Close cannot cancel a session whose handle the page never got (tools-panel.ts:683).
  • No bound for a silent tool: a tool that printed nothing was bounded only by its 5-minute lifetime, which then ended as a misleading "expired".
  • No journal: src/tools/login.ts wrote nothing on a start or an end.

The QR delivery itself was never broken: with --events, wacli prints qr_code as NDJSON on stderr whatever the TTY, and the Launchpad reads stderr. The tests prove this without a terminal.

What changes

  • The wacli probe reads wacli's envelope: data.authenticated must be true; the account label is data.phone, else data.linked_jid. A success:false answer, or the old top-level shape, is not signed in.
  • An already-paired wacli is reported as such: connected without a QR or pairing code first ends the sign-in, once the probe confirms it, as signed-in with already: true, usually within a second. The dialog says "{name} was already signed in on this Machine (as {account}); nothing was paired or changed." (Czech and English) and reads the card state again. wacli finishes its own sync to its idle exit in the background, as after a pairing. The CLI says the same.
  • An unconfirmed connected also ends with a reason: only the check confirms a sign-in. If it has not confirmed 20 s after connected (it is retried every 2 s), and always within the one-minute limit, and no code was shown, the process group is killed and the sign-in ends as failed with not-confirmed. A session that has shown a QR or pairing code keeps its 5-minute pairing lifetime, as before. The not-confirmed sentence now says the sign-in was not completed and was stopped, instead of "the tool ended".
  • Silent tools end with a reason: a sign-in that shows no link, code or QR code within one minute, and has not reported connected, is killed and ends as failed with the fixed code no-challenge. gh's "Link SSH key" is not affected; its steps have their own bounds.
  • The journal names every sign-in. The Launchpad writes one JSON line when a sign-in starts and one when it ends, with the tool, the outcome and the fixed reason code only. Example: {"scope":"tools-login","tool":"wacli","event":"end","outcome":"failed","reason":"no-challenge"}. It never carries output, a challenge, a phone number or a JID.
  • The dialog:
    • The status line and the detail never show the same sentence.
    • Each request is awaited for at most 45 s; after that the dialog ends with "The Launchpad did not answer in time", and a poll that gets no answer also cancels its session.
    • The endings not-installed, spawn-failed, tool-exit and no-challenge now say what to do next.
  • Account identifiers: the account label (the phone number, else the JID) appears only where a signed-in account label already appeared: the card and the "signed in" sentence, plain text of at most 120 characters (signInLabel). It never reaches the journal.
  • Docs:
    • docs/environment-tools.md: the probe shape, "already signed in", no-challenge and the journal lines.
    • docs/launchpad-development.md: the dialog.
    • An F19 addendum in docs/decisions.md, because how a session ends and what the probe reads changed.

What does not change

The wacli command lines, the QR drawing, the lifetimes, the composio and gh flows, the redaction rules in src/tools/redact.ts, and the rule that a challenge never reaches a log. No new process wrapper, pty or second state machine.

How it was verified

  • New route tests in tests/launchpad-wacli-sign-in.test.ts drive /api/tools/login/start|poll|cancel and /api/tools/status against stand-in wacli executables. HOME and XDG live in temporary directories, there is no network, and all values are fictional. The stand-ins:

    • replays the observed event stream of an already-paired wacli, with the idle exit shortened to 1.5 s. It ends as signed-in, already: true in under 5 s, the status route then reads signed in, and the process exits on its own idle;
    • prints a QR code late on stderr, with no terminal on any stream;
    • prints a QR code on stderr only, with stdout closed;
    • exits at once with a non-zero status (tool-exit);
    • prints nothing and keeps running (no-challenge within the bound, process killed);
    • is missing from PATH (not-installed);
    • reports connected while its status check keeps answering not signed in (the reviewer's case). It ends as not-confirmed within the bound, and the process is killed.

    Each test also checks the journal (start and end, no payload, no phone number).

  • The probe's shapes are in tests/tools.test.ts: signed in with phone and JID, JID only, not authenticated, a non-boolean flag, the former top-level shape, and the error envelope on stderr. The shared fake wacli in tests/fixtures/fake-login-tools.ts now prints the real envelope.

  • View tests in tests/tools-view.test.ts: the status and the detail differ in both languages, no-challenge and already are parsed and have their sentences, and a request that never settles is bounded.

  • On unchanged main, the already-paired replay ends as not-confirmed, the silent stand-in stays pending, and the others fail on the journal checks.

  • bun run check passes on 731eaae: lint (332 files), typecheck, bun test 1034 pass, 7 skip, 0 fail (1041 tests, 118 files), check:public PASS (389 files), proof build and smoke PASS. Two earlier runs under heavy machine load (load average about 30) each had 2 unrelated tests hit the 5 s bun timeout (in recover, git-checkout and legacy-paths); each of those passes when run alone.

What could NOT be verified

None of this was run with the real wacli binary, and no real WhatsApp pairing was done on a real Machine. Specifically:

  • The already-paired stream is a replay of the event kinds and order that were observed, not the tool's real data.
  • Whether a fresh pairing on that Machine's network shows its first QR code within one minute is not proven.
  • The observed wacli would, on main, have ended the dialog as not-confirmed after about 33 s. The report only describes the "Starting the sign-in…" phase, and that difference is not explained by anything available here. The journal line added by this PR will name the outcome of the next attempt.

Closes #98

🤖 Generated with Claude Code

…nal names it (#98)

The wacli sign-in dialog could stay on "Starting the sign-in…" forever:
the page awaited the start and poll requests without a deadline, showed
the same sentence as status and detail, and a tool that printed no QR
code was bounded only by the 5-minute pairing lifetime. Nothing reached
the Launchpad's journal.

- login.ts: a sign-in without a challenge within one minute is killed
  and fails as `no-challenge`; start and end of every sign-in go to an
  optional journal (tool, outcome, fixed reason code only).
- server.ts: the Launchpad journals them as JSON lines on stdout.
- Dialog: status and detail never repeat one sentence; each request is
  awaited at most 45 s; not-installed, spawn-failed, tool-exit and
  no-challenge say what to do (cs/en).
- Route tests with stand-in wacli executables: QR late on stderr without
  a terminal, QR on stderr only, exit at once, silent and running,
  missing from PATH.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s "already signed in" (#98)

On the Machine, wacli was paired before the attempt: `wacli auth
--events` connected without any `qr_code` event, and the Launchpad
listed it as not signed in. The catalog probe read `authenticated`,
`phone` and `linked_jid` at the top level of `wacli auth status --json`,
but wacli 0.18.2 wraps every JSON answer in {"success","data","error"}
(internal/out WriteJSON), so every paired wacli read as signed out and
the sign-in waited for a challenge that never comes.

- catalog.ts: the wacli probe reads data.authenticated, data.phone,
  data.linked_jid.
- login.ts: `connected` without a challenge first, confirmed by the
  probe, ends the session as signed-in with `already: true`; `connected`
  also suspends the no-challenge bound.
- Dialog and CLI say "already signed in"; the card state is re-read.
- Stand-ins print wacli's real envelope; a route test replays the
  observed event stream of an already paired wacli.
- Docs and the F19 addendum name the cause.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@immakermatty immakermatty changed the title A curated sign-in that shows nothing ends with a reason, and the journal names it (#98) wacli sign-in: a paired wacli reads as signed in, "already signed in" instead of waiting, and a sign-in that shows nothing ends with a reason (#98) Sep 29, 2026
@immakermatty
immakermatty marked this pull request as ready for review September 29, 2026 21:25

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty — exact-head review of 754b969. Changes requested for one reproducible failure of the promised bounded sign-in.

[P2] Keep the no-challenge deadline when connected is not confirmed — src/tools/login.ts:983-992.
The timer exempts every session that has merely emitted connected, even if the read-only status probe continues to say signed out or unknown and no challenge has appeared. A wacli stand-in that emits connected and then remains alive with auth status --json returning {success:true,data:{authenticated:false},error:null} still returns pending after the configured 500 ms challenge bound (observed at 800 ms through the actual /api/tools/login/start and /api/tools/login/poll routes); the expected failed/no-challenge assertion fails. With production defaults, this leaves the dialog on “Starting the sign-in…” and the process running until the five-minute lifetime, contradicting the one-minute termination and journal outcome promised for a sign-in with no challenge. Keep F19's status-probe confirmation as the only path to signed-in, but let an unconfirmed connected session with no QR/pair code hit the challenge deadline and kill its process group. Add this route-level regression alongside the silent-tool test; update the exception in the docs and F19 addendum accordingly.

The primary repair otherwise checks out: wacli v0.18.2 source wraps auth status --json under success/data/error, with authenticated and the optional phone/linked_jid inside data; the tests now use that envelope. A confirmed connected ends as already: true, while a QR challenge is delivered from stderr. The journal emits structured tool/event/outcome/fixed reason only, the account is confined to the existing signed-in surfaces, and redact.ts is untouched. The Czech/English dialog copy and 45-second start/poll response bounds are present. The documentation correctly discloses that real pairing and a browser exercise of this head have not been verified; its unconfirmed-connected exception is the specific mismatch above.

Local bun run check passed on the pristine 754b969 checkout: 1033 passed, 7 skipped, lint/typecheck/public guard/proof build/smoke passed. Both exact-head CI jobs, ubuntu-24.04 and macos-14, passed. The negative route probe above was a temporary test in my isolated clone only, not pushed or part of the PR. No merge performed.

…n 20 s (#98 review)

At 754b969 a wacli that reported `connected` while its status check never
confirmed, and that showed no code, switched the no-challenge bound off
and stayed pending until the 5-minute lifetime. Only the check confirms a
sign-in, so now: 20 s after the first `connected` without a code (retried
every 2 s), and at the latest at the one-minute bound, the process group
is killed and the session fails as `not-confirmed`. A session that has
shown a code keeps its pairing lifetime. The not-confirmed sentence no
longer claims the tool ended by itself. Route test with a stand-in whose
status check keeps answering not signed in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty — re-review on exact head 731eaae against 754b969: APPROVED. The sole defect from review 5358791450 is fixed. The unconfirmed connected path now keeps the no-challenge deadline and additionally fails as not-confirmed 20 s after its first connected event without a challenge; only the status probe can confirm sign-in. A shown QR or pairing code retains the pairing lifetime. The new actual Launchpad start/poll route test observes not-confirmed inside the bound, process termination, and the journal outcome; all seven wacli route tests pass. CLI/Launchpad wording and F19/docs reflect the bound. Local bun run check passed on the isolated exact-head clone: 1034 passed, 7 skipped, lint/typecheck/public guard/proof build/smoke green. Both exact-head CI check jobs (Ubuntu 24.04 and macOS 14) succeeded; no unresolved review threads. No real WhatsApp pairing or browser exercise claimed. No merge performed.

@immakermatty
immakermatty merged commit 8eaf475 into main Sep 29, 2026
3 checks passed
immakermatty added a commit that referenced this pull request Sep 29, 2026
…t wording

Conflicts resolved so both sides stay:
- src/launchpad/messages.ts: #100's longer not-installed sentence kept, in
  the new wording (en and cs); the other hunks take this branch's wording.
  #100's new strings follow the rule too: already signed in (2 en, 2 cs)
  and the no-challenge sentence (en, cs).
- src/tools/curated-cli.ts: #100's no-challenge and already-signed-in
  sentences say Environment.
- docs/decisions.md: main's entries unchanged (F19 addendum of #98, F25,
  F26), F27 after them.
- src/folder/render.ts, tests/folder-tools.test.ts: main is still at
  base-instructions-14; this branch stays base-instructions-15.
- src/modules/module-cli.ts and the snapshots: main's content equals this
  branch's base there; this branch's side taken, snapshots unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Launchpad Settings: sign-in of wacli stays on "Starting the sign-in…" and never shows the QR code

2 participants